import subprocess
import time
import sys
import io

# ================= 核心配置（适配：小工具+简单脚本+数据分析+小型Web）=================
# 精选38个核心包（按场景分类，无冗余，依赖自动处理）
PACKAGES = [
    # 1. 小工具/简单脚本必备
    "pyperclip",        # 剪贴板操作
    "python-dotenv",    # 读取.env配置
    "loguru",           # 日志工具
    "click",            # 命令行工具开发
    "tqdm",             # 进度条显示
    "colorama",         # 控制台颜色输出
    "python-dateutil",  # 日期处理增强
    "arrow",            # 人性化日期库
    "pyyaml",           # YAML配置解析
    "json5",            # 支持注释的JSON解析
    "screeninfo",       # 获取屏幕信息
    "mss",              # 高效截图
    
    # 2. 普通数据分析必备
    "numpy",            # 数值计算基础
    "pandas",           # 数据分析核心
    "matplotlib",       # 数据可视化
    "openpyxl",         # 读写Excel(.xlsx)
    "xlrd",             # 读取Excel(.xls)
    "faker",            # 生成测试假数据
    
    # 3. 小型Web项目必备
    "Flask",            # 轻量级Web框架
    "FastAPI",          # 高性能API框架
    "waitress",         # Flask部署服务器
    "uvicorn",          # FastAPI部署服务器
    "requests",         # HTTP请求库
    "requests-html",    # HTTP请求+HTML解析
    "python-multipart", # 表单/文件上传处理
    
    # 4. 文件处理必备
    "python-docx",      # 操作Word文档
    "docxtpl",          # Word模板渲染
    "xlwt",             # 写入Excel(.xls)
    "PyPDF2",           # 基础PDF处理
    "PyMuPDF",          # 轻量PDF/图片处理
    
    # 5. 通用辅助工具
    "bs4",              # HTML解析（BeautifulSoup）
    "lxml",             # 解析核心（提升bs4效率）
    "qrcode[pil]",      # 生成二维码
    "pillow",           # 图片处理
    "tenacity",         # 自动重试机制
]

# 国内镜像源（提升下载速度）
MIRRORS = [
    "https://pypi.tuna.tsinghua.edu.cn/simple",
    "https://mirrors.aliyun.com/pypi/simple/",
    "https://pypi.mirrors.ustc.edu.cn/simple/"
]

# 配置参数
MAX_RETRIES = 3  # 最大重试次数
RETRY_DELAY = 10  # 重试间隔（秒）
INSTALL_TIMEOUT = 300  # 单个包安装超时（秒）

# 需系统依赖的包（仅3个，提示简洁）
SYSTEM_DEPEND_TIPS = {
    "lxml": "注意：Linux需先安装依赖'sudo apt install libxml2-dev libxslt1-dev'",
    "python-multipart": "注意：Windows需安装VC++运行库（通常系统已自带）",
    "PyMuPDF": "注意：Linux需先安装依赖'sudo apt install libgl1-mesa-glx'"
}

# ================= 基础工具函数（仅用标准库）=================
def setup_encoding():
    """设置编码（兼容Windows中文）"""
    try:
        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
        sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
    except Exception as e:
        print(f"编码配置警告：{e}")

def extract_domain(mirror):
    """提取镜像源域名（用于trusted-host）"""
    try:
        if mirror.startswith(("http://", "https://")):
            no_proto = mirror.split("//")[1]
            domain = no_proto.split("/")[0]
            return domain
        return None
    except Exception as e:
        print(f"提取镜像源域名失败：{mirror} -> {e}")
        return None

def is_package_installed(package):
    """检查包是否已安装"""
    try:
        pure_name = package.split("[")[0].split("==")[0].split(">=")[0].split("<=")[0]
        subprocess.check_output(
            [sys.executable, "-c", f"import {pure_name}"],
            stderr=subprocess.STDOUT,
            timeout=10
        )
        print(f"包 {package} 已安装，跳过")
        return True
    except subprocess.CalledProcessError:
        return False
    except Exception as e:
        print(f"检查 {package} 状态失败：{e}，将继续安装")
        return False

# ================= 安装核心函数 =================
def install_package(package):
    """安装单个包"""
    if is_package_installed(package):
        return True

    if package in SYSTEM_DEPEND_TIPS:
        tip = SYSTEM_DEPEND_TIPS[package]
        print(tip)

    retry_count = 0
    while retry_count < MAX_RETRIES:
        try:
            msg = f"正在安装 {package}...（尝试 {retry_count + 1}/{MAX_RETRIES}）"
            print(msg)

            if retry_count < len(MIRRORS):
                mirror = MIRRORS[retry_count % len(MIRRORS)]
                domain = extract_domain(mirror)
                cmd = [sys.executable, "-m", "pip", "install", package, "-i", mirror]
                if domain:
                    cmd.extend(["--trusted-host", domain])
                print(f"使用镜像源：{mirror}（信任主机：{domain or '无'}）")
            else:
                cmd = [sys.executable, "-m", "pip", "install", package]
                print("切换至PyPI默认源")

            subprocess.check_call(
                cmd,
                timeout=INSTALL_TIMEOUT,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.STDOUT
            )

            success_msg = f"成功安装 {package}"
            print(success_msg)
            return True

        except subprocess.TimeoutExpired:
            err_msg = f"安装 {package} 超时（超过 {INSTALL_TIMEOUT} 秒）"
            print(err_msg)
        except subprocess.CalledProcessError as e:
            err_msg = f"安装 {package} 失败（返回码：{e.returncode}）"
            print(err_msg)
        except Exception as e:
            err_msg = f"安装 {package} 异常：{str(e)}"
            print(err_msg)

        retry_count += 1
        if retry_count < MAX_RETRIES:
            wait_msg = f"等待 {RETRY_DELAY} 秒后重试..."
            print(wait_msg)
            time.sleep(RETRY_DELAY)

    final_err_msg = f"安装 {package} 失败，已重试 {MAX_RETRIES} 次"
    print(final_err_msg)
    return False

def check_and_install_pip():
    """检查并安装/更新pip"""
    retry_count = 0
    while retry_count < MAX_RETRIES:
        try:
            check_msg = f"检查pip状态...（尝试 {retry_count + 1}/{MAX_RETRIES}）"
            print(check_msg)

            subprocess.check_call(
                [sys.executable, "-m", "pip", "--version"],
                timeout=30,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.STDOUT
            )
            print("pip已安装")

            try:
                print("正在更新pip到最新版本...")
                subprocess.check_call(
                    [sys.executable, "-m", "pip", "install", "--upgrade", "pip"],
                    timeout=120,
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.STDOUT
                )
                print("pip更新成功")
            except Exception as e:
                update_err_msg = f"pip更新失败：{str(e)}（不影响后续安装）"
                print(update_err_msg)
            return True

        except subprocess.CalledProcessError:
            print("pip未安装，正在安装...")
            try:
                subprocess.check_call(
                    [sys.executable, "-m", "ensurepip", "--upgrade", "--default-pip"],
                    timeout=120,
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.STDOUT
                )
                print("pip安装成功")
                return True
            except Exception as e:
                install_err_msg = f"pip安装失败：{str(e)}"
                print(install_err_msg)
                retry_count += 1
                if retry_count < MAX_RETRIES:
                    wait_msg = f"等待 {RETRY_DELAY} 秒后重试..."
                    print(wait_msg)
                    time.sleep(RETRY_DELAY)
        except Exception as e:
            err_msg = f"检查pip状态异常：{str(e)}"
            print(err_msg)
            retry_count += 1
            if retry_count < MAX_RETRIES:
                time.sleep(RETRY_DELAY)

    final_err_msg = f"pip安装失败，已重试 {MAX_RETRIES} 次"
    print(final_err_msg)
    return False

# ================= 主函数 =================
def main():
    setup_encoding()

    print("=" * 60)
    print("Python包批量安装脚本（适配：小工具+数据分析+小型Web项目）")
    print("=" * 60)

    if not check_and_install_pip():
        print("pip安装失败，无法继续安装其他包")
        return

    total = len(PACKAGES)
    print(f"\n开始安装 {total} 个核心Python包...")

    success = 0
    fail = 0
    for idx, package in enumerate(PACKAGES, 1):
        print(f"\n[{idx}/{total}] 处理包：{package}")
        if install_package(package):
            success += 1
        else:
            fail += 1

    print("\n" + "=" * 60)
    print("安装完成！")
    print(f"成功：{success} 个")
    print(f"失败：{fail} 个")
    if fail > 0:
        print("可根据提示安装系统依赖后，重新运行脚本（会跳过已安装包）")
    print("=" * 60)

if __name__ == "__main__":
    main()